# -*- coding: utf-8 -*-

import fcntl
import logging
import os
import select

LOG = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(message)s')

FILES = {}


def set_nonblocking(fd):
    """
    设置非阻塞状态
    读不到数据或写缓冲区已满等异常情况发生之后，立即返回，不要等待（阻塞）
    例如：
    int flags = fcntl(socket, F_GETFL, 0);
    fcntl(socket, F_SETFL, flags | O_NONBLOCK);  # 将 socket 设置为非阻塞方式
    fcntl(socket, F_SETFL, flags & ~O_NONBLOCK); # 将 socket 设置为阻塞方式
    """
    LOG.debug('set_nonblocking: %s', FILES.get(fd, fd))
    # 获取文件状态标记和文件访问模式
    flags = fcntl.fcntl(fd, fcntl.F_GETFL)
    # 设置文件状态标记，加上非阻塞
    fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)


def set_close_exec(fd):
    """
    设置 close-on-exec
    确保创建的子进程不会拥有这个句柄
    可能是为了保证线程安全
    """
    LOG.debug('set_close_exec: %s', FILES.get(fd, fd))
    # 获取文件描述符标记
    flags = fcntl.fcntl(fd, fcntl.F_GETFD)
    # 设置文件描述符标记，加上 CLOEXEC
    # fork 子进程可使用 fd，exec 之后就会关闭子进程中的 fd
    fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)


_EPOLLIN = 0x001
_EPOLLPRI = 0x002
_EPOLLOUT = 0x004
_EPOLLERR = 0x008
_EPOLLHUP = 0x010
_EPOLLRDHUP = 0x2000
_EPOLLONESHOT = (1 << 30)
_EPOLLET = (1 << 31)

NONE = 0
READ = _EPOLLIN
WRITE = _EPOLLOUT
ERROR = _EPOLLERR | _EPOLLHUP | _EPOLLRDHUP

HANDLERS = {}
EVENTS = {}

HUB = select.epoll()
FILES[HUB.fileno()] = 'HUB'
set_close_exec(HUB.fileno())


def add_handler(fd, handler, events):
    LOG.debug('监听句柄：%s, handler %r, events %r', FILES.get(fd, fd), handler, events)
    HANDLERS[fd] = handler
    HUB.register(fd, events | ERROR)


def remove_handler(fd):
    LOG.debug('停止监听句柄：%s', FILES.get(fd, fd))
    HANDLERS.pop(fd, None)
    EVENTS.pop(fd, None)
    try:
        HUB.unregister(fd)
    except (OSError, IOError) as error:
        LOG.debug('停止监听句柄：错误：%s', error, exc_info=True)


def main():
    LOG.debug('创建管道')
    r, w = os.pipe()
    FILES[r] = 'pipe:r'
    FILES[w] = 'pipe:w'
    set_nonblocking(r)
    set_nonblocking(w)
    set_close_exec(r)
    set_close_exec(w)

    # 对读写通道创建句柄
    # python3，必须用二进制打开，否则：
    # ValueError: can't have unbuffered text I/O
    waker_reader = os.fdopen(r, 'rb', 0)
    waker_writer = os.fdopen(w, 'wb', 0)
    FILES[waker_reader] = 'waker_reader'
    FILES[waker_writer] = 'waker_writer'

    def _read_waker(fd, events):
        LOG.debug('main._read_waker: %s, %r', FILES.get(fd, fd), events)
        try:
            while True:
                waker_reader.read()
        except IOError:
            pass

    def _wake():
        # stop 和 add_callback 时调用
        try:
            waker_writer.write('x')
        except IOError:
            pass

    # 看起来就是：r 句柄上数据输入时，从 r 句柄 read 所有数据
    add_handler(r, _read_waker, READ)
    LOG.debug(waker_reader.read())
    LOG.debug(waker_writer.write(b'hello world'))
    LOG.debug(waker_reader.read())
    LOG.debug(waker_writer.write(b'hello world'))


if __name__ == '__main__':
    main()
